﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace CloudStorageLight.Core
{
    public class FileBlobAdapter : BlobAdapterBase
    {

        internal string rootWindowsFilePath;
        internal string rootUriPath;
        internal string shareName;
        internal string userName;
        internal string password;



        public FileBlobAdapter(string rootPath, string windowsFilePath, string shareName = null, string userName = null, string password = null, string timezone = null)
        {
            this.RootPath = rootPath;
            this.shareName = shareName;
            this.userName = userName;
            this.password = password;
            this.rootWindowsFilePath =  Environment.ExpandEnvironmentVariables(windowsFilePath).Trim('\\') + "\\";
            if (!string.IsNullOrEmpty(shareName))
            {
                if (!Directory.Exists(shareName))
                {
                    if (Connect() != 0) throw new BlobException(BlobExceptionCode.NotAvailable);
                }
                this.rootWindowsFilePath = shareName + this.rootWindowsFilePath;
            }
            if (!Directory.Exists(this.rootWindowsFilePath)) Directory.CreateDirectory(rootWindowsFilePath);
            this.rootUriPath = BlobUtil.UnescapeDataString(new Uri(rootWindowsFilePath).AbsoluteUri);
            this.TimeZone = timezone ?? "UTC";
        }

        public string ConvertWindowsPathToUrlPath(string winPath)
        {
            Uri u = new Uri(winPath);
            if (!u.IsFile) throw new ArgumentException("winPath");
            string uriPath = BlobUtil.UnescapeDataString(u.AbsoluteUri);
            uriPath = uriPath.Substring(rootUriPath.Length);
            uriPath = RootPath + uriPath;
            return uriPath;
        }

        public string ConvertUrlPathToWindowsPath(string urlPath)
        {
            Uri u = new Uri(rootUriPath + urlPath.Substring(RootPath.Length));
            if (!u.IsFile) throw new ArgumentException("urlPath");
            string winPath = u.LocalPath + BlobUtil.UnescapeDataString(u.Fragment);
            return winPath;
        }




        /// <summary>
        /// 指定したファイルが存在するか
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        #pragma warning disable 1998
        protected override async Task<bool> ExistsAsyncInternal(string path)
        {
            if (path == "/") return true;
            var winFilePath = ConvertUrlPathToWindowsPath(path);
            return path.EndsWith("/") ? Directory.Exists(winFilePath) : File.Exists(winFilePath);
        }
        #pragma warning restore 1998

        /// <summary>
        /// Blobのコピー
        /// </summary>
        /// <param name="path"></param>
        /// <param name="fromUri"></param>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task CopyAsync(string path, BlobFile file)
        {

            var winFilePathDest = ConvertUrlPathToWindowsPath(path);
            Directory.CreateDirectory(Path.GetDirectoryName(winFilePathDest));
            var winFilePathSrc = ConvertUrlPathToWindowsPath(file.Path);
            File.Copy(winFilePathSrc, winFilePathDest, true);

        }
        #pragma warning restore 1998

        /// <summary>
        /// 削除
        /// </summary>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task<bool> DeleteAsync(BlobItem file)
        {
            var winFilePath = ConvertUrlPathToWindowsPath(file.Path);
            if (file is BlobDirectory)
            {
                if (!Directory.Exists(winFilePath)) return false;
                Directory.Delete(winFilePath);

            }
            else
            {
                if (!File.Exists(winFilePath)) return false;
                File.Delete(winFilePath);
            }
            return true;
        }
        #pragma warning restore 1998

        /// <summary>
        /// ディレクトリののファイル一覧を取得する
        /// </summary>
        /// <param name="id"></param>
        /// <param name="maxSize"></param>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task<List<BlobItem>> GetFilesAsync(string id, int maxSize, bool flat = false)
        {
            var result = new List<BlobItem>();

            var children = new List<BlobItem>();
            var winFilePath = ConvertUrlPathToWindowsPath(id.StartsWith(RootPath) ? id : RootPath);
            var dire = winFilePath;
            var fList = Directory.GetFiles(dire, "*", flat ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
            var dList = Directory.GetDirectories(dire, "*", flat ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
            var fileList = dList.Select(x => x + "\\").Concat(fList).ToList();
            fileList.Insert(0, winFilePath);

            foreach (var file in fileList)
            {
                var logicalPath = ConvertWindowsPathToUrlPath(file);
                if (logicalPath.StartsWith(id))
                {
                    BlobItem fileItem;
                    if (file.EndsWith("\\"))
                    {
                        fileItem = new BlobDirectory(Parent, logicalPath);
                    }
                    else
                    {
                        fileItem = new BlobFile(Parent, logicalPath);
                        var info = new FileInfo(file);
                        fileItem.Length = info.Length;
                        fileItem.LastModified = ConvertTimezone(info.LastWriteTimeUtc);
                    }

                    result.Add(fileItem);
                    var access = File.GetAccessControl(file);
                    try
                    {
                        fileItem.Owner = access.GetOwner(typeof(SecurityIdentifier)).Translate(typeof(NTAccount)).Value;
                    }
                    catch { }
                    if (maxSize > 0 && result.Count >= maxSize)
                    {
                        break;
                    }
                }
            }
            return result;
        }
        #pragma warning restore 1998


        /// <summary>
        /// アップロード
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public override async Task UploadAsync(BlobItem item, Stream stream)
        {
            var winFilePath = ConvertUrlPathToWindowsPath(item.Path);
            Directory.CreateDirectory(Path.GetDirectoryName(winFilePath));

            using (var st = new FileStream(winFilePath, FileMode.Create))
            {
                await stream.CopyToAsync(st);
            }
            ClearExistsCahce();
        }

        /// <summary>
        /// アップロード
        /// </summary>
        /// <param name="localpath"></param>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task UploadAsync(BlobItem item, string localpath)
        {
            var winFilePath = ConvertUrlPathToWindowsPath(item.Path);
            Directory.CreateDirectory(Path.GetDirectoryName(winFilePath));

            File.Copy(localpath, winFilePath, true);
            ClearExistsCahce();
        }
        #pragma warning restore 1998

        /// <summary>
        /// ダウンロード
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public override async Task DownloadAsync(BlobItem item, Stream stream)
        {
            var winFilePath = ConvertUrlPathToWindowsPath(item.Path);

            using (var st = new FileStream(winFilePath, FileMode.Open))
            {
                await st.CopyToAsync(stream);
            }
        }

        /// <summary>
        /// ダウンロード
        /// </summary>
        /// <param name="localpath"></param>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task DownloadAsync(BlobItem item, string localpath)
        {
            var winFilePath = ConvertUrlPathToWindowsPath(item.Path);

            File.Copy(winFilePath, localpath, true);
        }
        #pragma warning restore 1998

        /// <summary>
        /// フォルダ作成
        /// </summary>
        /// <param name="dire"></param>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task CreateDirectoryAsync(BlobDirectory dire)
        {
            var winFilePath = ConvertUrlPathToWindowsPath(dire.Path);
            Directory.CreateDirectory(winFilePath);
            ClearExistsCahce();
        }
        #pragma warning restore 1998

        /// <summary>
        /// メタ情報の設定
        /// </summary>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task SetMetadataAsync(BlobItem item)
        {
            item.Link = ConvertUrlPathToWindowsPath(item.Path);

        }
        #pragma warning restore 1998


        /// <summary>
        /// メタ情報の取得
        /// </summary>
        /// <param name="fetch"></param>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task GetMetadataAsync(BlobItem file)
        {
            var winFilePath = ConvertUrlPathToWindowsPath(file.Path);
            if (!File.Exists(winFilePath)) return;
            if (winFilePath.EndsWith("\\"))
            {
                var info = new DirectoryInfo(winFilePath);
                file.LastModified = ConvertTimezone(info.LastWriteTimeUtc);
            }
            else
            {
                var info = new FileInfo(winFilePath);
                file.Length = info.Length;
                file.LastModified = ConvertTimezone(info.LastWriteTimeUtc);
            }
            try
            {
                var access = File.GetAccessControl(winFilePath);
                file.Owner = access.GetOwner(typeof(SecurityIdentifier)).Translate(typeof(NTAccount)).Value;
            }
            catch { }
        }
        #pragma warning restore 1998


        /// <summary>
        /// 後処理
        /// </summary>
        public override void Dispose()
        {
            if(!string.IsNullOrEmpty(shareName))
            {
                Disconnect();
            }
        }

        private int Connect()
        {
            NETRESOURCE nr = new NETRESOURCE();
            nr.dwType = ResourceType.RESOURCETYPE_DISK;
            nr.lpLocalName = null;
            nr.lpRemoteName = shareName;
            nr.lpProvider = null;

            return  WNetAddConnection2(nr, password, userName, 0);
        }

        const int CONNECT_UPDATE_PROFILE = 0x00000001;

        private void Disconnect()
        {
            WNetCancelConnection2(shareName, CONNECT_UPDATE_PROFILE, false);
        }

        [DllImport("Mpr.dll", EntryPoint = "WNetAddConnection2", CallingConvention = CallingConvention.Winapi)]
        private static extern int WNetAddConnection2(NETRESOURCE lpNetResource, string lpPassword,
                                      string lpUsername, System.UInt32 dwFlags);

        [DllImport("Mpr.dll")]
        private static extern int WNetCancelConnection2(
            string lpName,
            int dwFlags,
            bool fForce
        );

    }

    [StructLayout(LayoutKind.Sequential)]
    internal class NETRESOURCE
    {
        public ResourceScope dwScope = 0;
        public ResourceType dwType = 0;
        public ResourceDisplayType dwDisplayType = 0;
        public ResourceUsage dwUsage = 0;
        public string lpLocalName = null;
        public string lpRemoteName = null;
        public string lpComment = null;
        public string lpProvider = null;
    };

    public enum ResourceScope
    {
        RESOURCE_CONNECTED = 1,
        RESOURCE_GLOBALNET,
        RESOURCE_REMEMBERED,
        RESOURCE_RECENT,
        RESOURCE_CONTEXT
    };

    public enum ResourceType
    {
        RESOURCETYPE_ANY,
        RESOURCETYPE_DISK,
        RESOURCETYPE_PRINT,
        RESOURCETYPE_RESERVED
    };

    public enum ResourceUsage
    {
        RESOURCEUSAGE_CONNECTABLE = 0x00000001,
        RESOURCEUSAGE_CONTAINER = 0x00000002,
        RESOURCEUSAGE_NOLOCALDEVICE = 0x00000004,
        RESOURCEUSAGE_SIBLING = 0x00000008,
        RESOURCEUSAGE_ATTACHED = 0x00000010,
        RESOURCEUSAGE_ALL = (RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED),
    };

    public enum ResourceDisplayType
    {
        RESOURCEDISPLAYTYPE_GENERIC,
        RESOURCEDISPLAYTYPE_DOMAIN,
        RESOURCEDISPLAYTYPE_SERVER,
        RESOURCEDISPLAYTYPE_SHARE,
        RESOURCEDISPLAYTYPE_FILE,
        RESOURCEDISPLAYTYPE_GROUP,
        RESOURCEDISPLAYTYPE_NETWORK,
        RESOURCEDISPLAYTYPE_ROOT,
        RESOURCEDISPLAYTYPE_SHAREADMIN,
        RESOURCEDISPLAYTYPE_DIRECTORY,
        RESOURCEDISPLAYTYPE_TREE,
        RESOURCEDISPLAYTYPE_NDSCONTAINER
    };

}
